package com.googlecode.jnarwhal.common;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import com.googlecode.jnarwhal.event.EventManager;
import com.googlecode.jnarwhal.input.InputManager;

/**
 * Basic game object
 * @author Chris
 *
 */
public abstract class Game {

	private boolean gameRunning = false;	

	private static final int DEFAULT_DISPLAY_WIDTH = 800;
	private static final int DEFAULT_DISPLAY_HEIGHT = 600;
	private static final int NUMBER_OF_THREAD_POOLS = 4;

	public static Logger logger = Logger.getLogger("jnarwhal");
	public static ExecutorService threadPool = Executors.newFixedThreadPool(NUMBER_OF_THREAD_POOLS);


	/**
	 * User defined initialization
	 */
	public abstract void customInit();

	/**
	 * User defined game logic update
	 * @param delta
	 */
	public abstract void customUpdate(double delta);

	/**
	 * used defined rendering
	 */
	public abstract void customRender();

	/**
	 * Start the game
	 */
	public final void start() {
		logger.info("starting game thread");
		new Thread() {
			@Override
			public void run() {
				gameRunning = true;
				init();
				customInit();
				gameLoop();
			}
		}.start();
	}

	/**
	 * initialize the game
	 */
	protected void init() {
		// setup the display
		logger.info("setting up lwjgl display");
		try {
			Display.setDisplayMode(new DisplayMode(DEFAULT_DISPLAY_WIDTH,
					DEFAULT_DISPLAY_HEIGHT));
			Display.create();
		} catch (LWJGLException e) {
			logger.severe("could not create display, reason: " + e.getMessage());
			System.exit(0);
		}

		// init OpenGL here
		logger.info("initializing OpenGL");

		// init eventsystem
		logger.info("initializing event system");
		EventManager.init();
		
		// init inputsystem
		logger.info("initializing input system");
		InputManager.init();
	}

	/**
	 * update game logic
	 * @param delta used for time calculations
	 */
	protected void update(double delta) {
		// display close request
		if (Display.isCloseRequested()) {
			// call close Event
			gameRunning = false;
		}
		// poll for new input
		InputManager.pollInput();
		
		// handle all queued events
		EventManager.handleEvents(delta);
	}

	/**
	 * render the scene
	 */
	protected void render() {
		// update the display
		Display.update();
	}

	/**
	 * Main Game Loop
	 */
	protected void gameLoop() {
		logger.info("starting game loop");
		final int TARGET_FPS = 60;
		final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
		long lastLoopTime = System.nanoTime();
		long lastFpsTime = 0;
		int fps = 0;

		// Loop until the game ends
		while (gameRunning) {

			// determine how long it's been since the last update.
			long now = System.nanoTime();
			long updateLength = now - lastLoopTime;
			lastLoopTime = now;
			// this is used to calculate how far entities should move and so on
			double delta = updateLength / ((double) OPTIMAL_TIME);

			// calculate fps if a second has past
			lastFpsTime += updateLength;
			fps++;
			if (lastFpsTime >= 1000000000) {
				Display.setTitle("FPS: " + String.valueOf(fps));
				lastFpsTime = 0;
				fps = 0;
			}

			// do update
			update(delta);
			customUpdate(delta);

			// do render
			render();
			customRender();

			// sleep a bit to obtain the desired framerate
			Display.sync(TARGET_FPS);
		}
		//shut down
		shutdown();
	}
	
	/**
	 * Clean up and shut down
	 */
	protected void shutdown() {
		threadPool.shutdown();
	}

}
